package com.af.data.jpa.configurer;

import com.af.data.jpa.annotation.AfJpaDto;
import com.af.system.enums.AfNamingRuleEnum;
import com.af.system.util.AfDateUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.FatalBeanException;
import org.springframework.context.ApplicationContext;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;

/**
 * @author : zhenyun.su
 * @comment : 注入JpaDto转换器
 * 为AfJpaDto注解的实体类注册转换器，
 * 实现将oracle查询结果Map<String, Object>(栏位大写，值Object类型)转换为实体类(小写驼峰，不同的类型)。
 * @since : 2022/7/26
 */

public class AfJpaDtoConfigurer {
    private ApplicationContext applicationContext;

    public AfJpaDtoConfigurer(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    public void init() {
        Map<String, Object> map = applicationContext.getBeansWithAnnotation(AfJpaDto.class);
        for (Object o : map.values()) {
            Class c = o.getClass();
            GenericConversionService genericConversionService = ((GenericConversionService) DefaultConversionService.getSharedInstance());
            genericConversionService.addConverter(Map.class, c, m -> {
                try {
                    Object obj = c.newInstance();
                    AfJpaDto afJpaDto = (AfJpaDto) c.getAnnotation(AfJpaDto.class);
                    AfNamingRuleEnum ruleEnum= afJpaDto.strategy();

                    return copyMapToObj(m, obj, ruleEnum);
                } catch (Exception e) {
                    throw new FatalBeanException("Jpa结果转换出错,class=" + c.getName(), e);
                }
            });
        }
    }

    /*
     * @comment : 将map中的值copy到bean中对应的字段上
     * map参数的String如果大写， 则AfNamingRuleEnum.uline， 如果小写则lline
     */
    private Object copyMapToObj(Map<String, Object> map, Object target, AfNamingRuleEnum ruleEnum) {
        if(map == null || target == null || map.isEmpty()){
            return target;
        }
        Class<?> actualEditable = target.getClass();
        PropertyDescriptor[] targetPds = BeanUtils.getPropertyDescriptors(actualEditable);
        for (PropertyDescriptor targetPd : targetPds) {
            if(targetPd.getWriteMethod() == null) {
                continue;
            }
            try {
                String key = targetPd.getName();
                // Class clazz =targetPd.getPropertyType();
                if (AfNamingRuleEnum.uline == ruleEnum){
                    key= ruleEnum.naming(key);
                }
                Object value = map.get(key);
                if (value == null) {
                    continue;
                }else{
                    // to do list 添加更多类型转换
                    if (targetPd.getPropertyType().getName().contains("Long")){
                        value = Long.valueOf(value.toString());
                    }else if (targetPd.getPropertyType().getName().contains("LocalDateTime")){
                        value = AfDateUtils.ofDate(value.toString().substring(0, 19), "yyyy-MM-dd HH:mm:ss");
                    }else if (targetPd.getPropertyType().getName().contains("Integer")){
                        value = Integer.valueOf(value.toString());
                    }else if (targetPd.getPropertyType().getName().contains("Boolean")){
                        value = "0".equals(value.toString()) || "false".equalsIgnoreCase(value.toString()) ? false : true;
                    }
                }
                Method writeMethod = targetPd.getWriteMethod();
                if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                    writeMethod.setAccessible(true);
                }
                writeMethod.invoke(target, value);
            } catch (Exception ex) {
                throw new FatalBeanException("Could not copy "+targetPd.getName()+"property from source to target, according to "+targetPd.getPropertyType().getName(), ex);
            }
        }
        return target;
    }
}
